﻿using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Linq;

namespace MLSharp.Utilities
{
	/// <summary>
	/// This is a simple class that provides access to data sets that can be
	/// used for unit testing.
	/// </summary>
	public static class TestDataSets
	{
		/// <summary>
		/// Returns a data set where the target attribute is a flag indicating
		/// whether the number is divisible by three.
		/// </summary>
		/// <returns>
		/// A data set containing 500 instances with a label (the number), 32 bit (0,1) fields, and 
		/// a value that is 1 if the number is divisible by three and 0 otherwise.
		/// </returns>
		public static IDataSet GetDivisibleByThreeDataSet()
		{
			BasicDataSet ds = new BasicDataSet();

			for (int i=0; i < 32; i++)
			{
				ds.Attributes.Add(DataAttribute.NewSetAttribute("Bit" + i, "1", "0"));
			}

			ds.Attributes.Add(DataAttribute.NewSetAttribute("DivisibleBy3", "1", "0"));

			ds.TargetAttributeIndex = 32;

			for (int i=0; i < 500; i++)
			{
				double[] values = new double[33];

				BitArray array = new BitArray(new [] {i});

				//Copy the bits into the values array.
				for (int j=0; j < array.Length; j++)
				{
					values[j] = array[j] ? 1 : 0;
				}

				//Set the class value
				values[32] = i%3 == 0 ? 1 : 0;

				ds.Instances.Add(new Instance(i.ToString(), values));
			}

			return ds;
		}

		/// <summary>
		///	This is a simple data set that basically says:
		/// If A XOR B then 1 else 0.  The target attribute
		/// is at index 4.
		/// </summary>
		/// <returns>A data set with 100 instances with the attributes ID,
		/// A, B, Noise, and Outcome (1 if A XOR B, 0 otherwise).</returns>
		public static IDataSet GetXOrDataSet()
		{
			IDataSet trainingSet = new BasicDataSet();
			trainingSet.Attributes.Add(DataAttribute.NewSetAttribute("A", "0", "1"));
			trainingSet.Attributes.Add(DataAttribute.NewSetAttribute("B", "0", "1"));
			trainingSet.Attributes.Add(DataAttribute.NewContinuousAttribute("Noise"));
			trainingSet.Attributes.Add(DataAttribute.NewSetAttribute("Outcome", "0", "1"));
			trainingSet.TargetAttributeIndex = 3;

			Random rand = new Random(5);

			for (int i = 0; i < 100; i++)
			{
				//Generate random values for A and B
				int a = rand.Next() % 2;
				int b = rand.Next() % 2;
				int output = a ^ b;

				Instance instance = new Instance(i.ToString(), a, b, rand.Next(), output);

				trainingSet.Instances.Add(instance);
			}

			return trainingSet;
		}

		/// <summary>
		/// Gets a data set where the target label indicates whether or
		/// not another attribute is an even or odd number.
		/// </summary>
		/// <returns>The data set.</returns>
		/// <remarks>
		/// This data set is basically useless in terms of validating predictions, but
		/// it can be used to verify other IDataSet-related behaviors.
		/// </remarks>
		public static IDataSet GetEvenOddDataSet()
		{
			IDataSet trainingSet = new BasicDataSet();
			trainingSet.Attributes.Add(DataAttribute.NewContinuousAttribute("Value"));
			trainingSet.Attributes.Add(DataAttribute.NewSetAttribute("Even", "0", "1"));
			trainingSet.TargetAttributeIndex = 1;

			trainingSet.Instances.Add(new Instance("1", 1, 0));
			trainingSet.Instances.Add(new Instance("2", 2, 1));
			trainingSet.Instances.Add(new Instance("3", 3, 0));
			trainingSet.Instances.Add(new Instance("4", 4, 1));
			trainingSet.Instances.Add(new Instance("5", 5, 0));
			trainingSet.Instances.Add(new Instance("6", 6, 1));
			trainingSet.Instances.Add(new Instance("7", 7, 0));
			trainingSet.Instances.Add(new Instance("8", 8, 1));
			trainingSet.Instances.Add(new Instance("9", 9, 0));
			trainingSet.Instances.Add(new Instance("10", 10, 1));
			return trainingSet;
		}

		/// <summary>
		/// Gets the Sin data set.
		/// </summary>
		/// <returns></returns>
		public static IDataSet GetSinDataSet()
		{
			IDataSet dataSet = new BasicDataSet();
			dataSet.Attributes.Add(DataAttribute.NewContinuousAttribute("Input"));
			dataSet.Attributes.Add(DataAttribute.NewContinuousAttribute("Output"));
			dataSet.TargetAttributeIndex = 1;
            
			using (StreamReader reader = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream(typeof(TestDataSets), "SinDataSet.txt")))
			{
				while (!reader.EndOfStream)
				{
					string line = reader.ReadLine();

					double[] values =
						(line.Split(new[] {" "}, StringSplitOptions.RemoveEmptyEntries)).Select(val => double.Parse(val)).ToArray();

					dataSet.Instances.Add(new Instance((dataSet.Instances.Count + 1).ToString(), values[0], values[1]));
				}
			}

			return dataSet;
		}
	}
}
